"""
Modified from SimpleHTTPRequestHandler.py

Now supports WebSockets w00t!
"""

import os
import SWSHandler
import urllib
import posixpath
import cgi
import mimetypes
import struct
import hashlib
import BufferFile
#import BaseHTTPServer
#import shutil

try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO

ROOT_FILES_PATH="" #set this before you run the server, if you do not want
                   #to host any files, leave blank
                   
LOADED_MODULES={}  #add application modules to this directory using the format:
                   # "URI":ApplicationClass

class SWSHTTPHandler(SWSHandler.SWSHandler):

    """Simple HTTP request handler with GET and HEAD commands.

    This serves files from the current directory and any of its
    subdirectories.  The MIME type for files is determined by
    calling the .guess_type() method.

    The GET and HEAD requests are identical except that the HEAD
    request omits the actual contents of the file.

    """
    
    def __init__(self,con):
        SWSHandler.SWSHandler.__init__(self,con)
        self.reqfile=None
        self.key1=0
        self.key2=0
        self.ws_code=""
        

    def do_GET(self):
        """ Serve a GET request.
        
            Switch to Web Sockets Hand Shaking Mode if needed."""
            
        if self.path in LOADED_MODULES:
            
            #check for websockets
            if ("Connection" in self.headers 
                and self.headers["Connection"]=="Upgrade"
                and "Upgrade" in self.headers
                and self.headers["Upgrade"]=="WebSocket"):
                
                print("Websockets Request Detected!")
                
                if "Origin" in self.headers and "Host" in self.headers:
                    origin=self.headers["Origin"]
                    host=self.headers["Host"]
                    origin_address,port=self.con.soc.getpeername()
                    
                    #create responses
                    response_origin="%s"%(origin,)
                    response_location="ws://%s%s"%(host,self.path)
                    
                else:
                    self.send_response(405)
                    return
                    
                try:#look for security keys
                    self.key1=self.decode_key(self.headers["Sec-WebSocket-Key1"])
                    self.key2=self.decode_key(self.headers["Sec-WebSocket-Key2"])
                    
                except KeyError: # no security request ...
                    print("Security codes missing, skipping authentation")
                    self.key1=0
            
                self.wfile.write("HTTP/1.1 101 Web Socket Protocol Handshake\r\n")
                self.send_header("Upgrade", "WebSocket")
                self.send_header("Connection", "Upgrade")
                
                if(self.key1):
                    self.send_header("Sec-WebSocket-Origin",origin)
                    self.send_header("Sec-WebSocket-Location",response_location)
                    self.send_header("Sec-WebSocket-Protocol","sample")
                
                else:#no security keys means we are using old version of protocol
                    self.send_header("WebSocket-Origin",origin)
                    self.send_header("WebSocket-Location",response_location)
                    self.send_header("WebSocket-Protocol","sample")
                    
                self.wfile.write("\r\n\r\n")
                
                self.wfile.write("\x00\xFF")#send initial byte (...?)
                self.read_mode=1 #following call of handle_data will do the rest
                
            else: #err unsupported method?
                self.send_response(405)
        
        else:
            self.reqfile = self.send_head();
                    

    def do_HEAD(self):
        """Serve a HEAD request."""
        f = self.send_head()
        if f:
            f.close()
            
            
    def do_file_io(self):
        """ Execute pending file io"""
        
        if not self.reqfile: #no io waiting
            return
        
        try:
            if self.wfile.readfrom(self.reqfile): #true on eof
                self.reqfile.close()
                self.reqfile=None
                
        except IOError:
            print("WARNING: IO error in SWSHTTPHandler")
            self.reqfile=None
            
        
    def handle_data(self):
        """Handles special data modes:
        
        1- Websockets handshake security processing
        2- Websockets recieve data frame
        
        TODO:
        3- reading request body for POST
        4- stream the request body to an app module,
            e.g. for downloading files from client """
            
        if self.read_mode==1:
        
            if(self.key1):
                self.ws_code+=self.rfile.read(8)
            
                if len(self.ws_code)<8:
                    return; #wait until we have entire key next time
                    
                #now concat the keys into one long string
                combined=struct.pack("long",self.key1)+\
                    struct.pack("long",self.key2)+self.ws_code
                
                #calculate MD5 sum
                hash=hashlib.md5()
                hash.update(combined)
        
                #send the security response
                self.wfile.write(hash.digest())
                
            LOADED_MODULES[self.path].start_connection(self) #indicate new connection
            self.read_mode=2 #hand off to data processing mode
                
        elif self.read_mode==2:#processing a websockets frame
        
            found,data=self.rfile.readto("\xFF") #see if we have an enitre frame
            
            while(found):
            
                if(len(data)==0): #closing frame
                    print("Closing handshake recieved.")
                    self.wfile.write("\xFF")
                    self.mode==0
                    
                else:
                    LOADED_MODULES[self.path].process_data(self,data)
                    found,data=self.rfile.readto("\xFF") #see if we have more frames waiting
        

	
    def handle_adobe(self):
        """Response to adobe flash security request.
            
        Sends them the security protocol"""
        
        loc,port=self.con.soc.getpeername()
        loc,port=self.con.soc.getpeername()
        self.wfile.write("<?xml version=\"1.0\"?> \
            <!DOCTYPE cross-domain-policy SYSTEM \"/xml/dtds/cross-domain-policy.dtd\">\
            <cross-domain-policy>\
            <site-control permitted-cross-domain-policies=\"master-only\"/>\
            <allow-access-from domain=\"daliempire.net\" to-ports=\"8787\" />\
            </cross-domain-policy>")
            
        #path=os.path.join(ROOT_FILES_PATH,"adobe_response.txt")
        #self.reqfile=open(path,"rb")
        print("Sending Adobe Security Response")
        
    def handle_disconnect(self):
        """Tell the module that we are stopping"""
        
        if self.path in LOADED_MODULES:
            LOADED_MODULES[self.path].stop_connection(self)
            
            
    def ws_send(self,data):
        """Called by application module to send a text frame"""
        
        self.wfile.write("\x00%s\xFF"%(data,))
            
            
    def decode_key(self,key):
        """Decode a key for the WebSockets handshake"""
        
        number=""
        num_spaces=0
        
        for i in range(len(key)):
        
            if key[i].isdigit():
                number+=key[i]
                
            elif key[i]==" ":
                num_spaces+=1
                
        return int(number)/num_spaces

        
    def send_head(self):
        """Common code for GET and HEAD commands.

        This sends the response code and MIME headers.

        Return value is either a file object (which has to be copied
        to the outputfile by the caller unless the command was HEAD,
        and must be closed by the caller under all circumstances), or
        None, in which case the caller has nothing further to do.

        """
        path = self.translate_path(self.path)
        f = None
        if os.path.isdir(path):
            if not self.path.endswith('/'):
                # redirect browser - doing basically what apache does
                self.send_response(301)
                self.send_header("Location", self.path + "/")
                self.end_headers()
                return None
            for index in "index.html", "index.htm":
                index = os.path.join(path, index)
                if os.path.exists(index):
                    path = index
                    break
            else:
                return self.list_directory(path)
        ctype = self.guess_type(path)
        try:
            # Always read in binary mode. Opening files in text mode may cause
            # newline translations, making the actual size of the content
            # transmitted *less* than the content-length!
            f = open(path, 'rb')
        except IOError:
            self.send_error(404, "File not found")
            return None
        self.send_response(200)
        self.send_header("Content-type", ctype)
        fs = os.fstat(f.fileno())
        self.send_header("Content-Length", str(fs[6]))
        self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
        self.end_headers()
        return f

    def list_directory(self, path):
        """Helper to produce a directory listing (absent index.html).

        Return value is either a file object, or None (indicating an
        error).  In either case, the headers are sent, making the
        interface the same as for send_head().

        """
        try:
            list = os.listdir(path)
        except os.error:
            self.send_error(404, "No permission to list directory")
            return None
        list.sort(key=lambda a: a.lower())
        f = StringIO()
        displaypath = cgi.escape(urllib.unquote(self.path))
        f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
        f.write("<html>\n<title>Directory listing for %s</title>\n" % displaypath)
        f.write("<body>\n<h2>Directory listing for %s</h2>\n" % displaypath)
        f.write("<hr>\n<ul>\n")
        for name in list:
            fullname = os.path.join(path, name)
            displayname = linkname = name
            # Append / for directories or @ for symbolic links
            if os.path.isdir(fullname):
                displayname = name + "/"
                linkname = name + "/"
            if os.path.islink(fullname):
                displayname = name + "@"
                # Note: a link to a directory displays with @ and links with /
            f.write('<li><a href="%s">%s</a>\n'
                    % (urllib.quote(linkname), cgi.escape(displayname)))
        f.write("</ul>\n<hr>\n</body>\n</html>\n")
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        return f

    def translate_path(self, path):
        """Translate a /-separated PATH to the local filename syntax.

        Components that mean special things to the local file system
        (e.g. drive or directory names) are ignored.  (XXX They should
        probably be diagnosed.)

        """
        # abandon query parameters
        path = path.split('?',1)[0]
        path = path.split('#',1)[0]
        path = posixpath.normpath(urllib.unquote(path))
        words = path.split('/')
        words = filter(None, words)
        path = ROOT_FILES_PATH
        for word in words:
            drive, word = os.path.splitdrive(word)
            head, word = os.path.split(word)
            if word in (os.curdir, os.pardir): continue
            path = os.path.join(path, word)
        return path

        
    def guess_type(self, path):
        """Guess the type of a file.

        Argument is a PATH (a filename).

        Return value is a string of the form type/subtype,
        usable for a MIME Content-type header.

        The default implementation looks the file's extension
        up in the table self.extensions_map, using application/octet-stream
        as a default; however it would be permissible (if
        slow) to look inside the data to make a better guess.

        """

        base, ext = posixpath.splitext(path)
        if ext in self.extensions_map:
            return self.extensions_map[ext]
        ext = ext.lower()
        if ext in self.extensions_map:
            return self.extensions_map[ext]
        else:
            return self.extensions_map['']

            
    if not mimetypes.inited:
        mimetypes.init() # try to read system mime.types
    extensions_map = mimetypes.types_map.copy()
    extensions_map.update({
        '': 'application/octet-stream', # Default
        '.py': 'text/plain',
        '.c': 'text/plain',
        '.h': 'text/plain',
        })